Skip to main content

Authentication

Mission Control supports two authentication modes: Local (single-token) and Clerk (multi-user JWT).

Authentication Modes

Local Mode

Local mode uses a single shared bearer token for self-hosted deployments. This is ideal for personal instances.

Configuration

Set these environment variables: Backend (backend/.env)
AUTH_MODE=local
LOCAL_AUTH_TOKEN=<your-token-min-50-chars>
Frontend (frontend/.env.local)
NEXT_PUBLIC_AUTH_MODE=local
NEXT_PUBLIC_LOCAL_AUTH_TOKEN=<same-token-as-backend>
The token must be at least 50 characters long. Frontend and backend tokens must match exactly.

How It Works

  1. Browser sends requests with Authorization: Bearer <LOCAL_AUTH_TOKEN>
  2. Backend compares using constant-time comparison (line 422 in auth.py)
  3. If valid, creates/returns user with email admin@home.local
  4. User is automatically assigned to an organization
Source: backend/app/core/auth.py:410-427
async def _resolve_local_auth_context(
    *,
    request: Request,
    session: AsyncSession,
    required: bool,
) -> AuthContext | None:
    token = _extract_bearer_token(request.headers.get("Authorization"))
    expected = settings.local_auth_token.strip()
    if not expected or not compare_digest(token, expected):
        if required:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
        return None
    user = await _get_or_create_local_user(session)
    return AuthContext(actor_type="user", user=user)

Clerk Mode

Clerk mode enables multi-user authentication with JWT verification.

Configuration

Backend (backend/.env)
AUTH_MODE=clerk
CLERK_SECRET_KEY=sk_test_...
CLERK_API_URL=https://api.clerk.com
CLERK_VERIFY_IAT=true
CLERK_LEEWAY=10.0
Frontend (frontend/.env.local)
NEXT_PUBLIC_AUTH_MODE=clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/boards
NEXT_PUBLIC_CLERK_AFTER_SIGN_OUT_URL=/

How It Works

  1. Browser obtains JWT from Clerk UI
  2. Sends requests with Authorization: Bearer <jwt>
  3. Backend verifies JWT signature using Clerk public key
  4. Extracts sub (user ID), email, and name from JWT claims
  5. Creates/updates user record in database
  6. Ensures user is a member of an organization
Source: backend/app/core/auth.py:435-474

Agent Authentication

Agents authenticate using X-Agent-Token header instead of bearer tokens.

Agent Token Flow

1

Agent provisioning

When an agent is created, Mission Control generates a unique token and stores its hash in agents.agent_token_hash.
2

Template sync

The token is written to the agent’s TOOLS.md file as AUTH_TOKEN.
3

Agent requests

Agent reads AUTH_TOKEN from TOOLS.md and includes it as:
curl -H "X-Agent-Token: $AUTH_TOKEN" \
     "$BASE_URL/api/v1/agent/boards"
4

Verification

Backend hashes the incoming token and compares against agent_token_hash.

Agent-Accessible Endpoints

Endpoints under /api/v1/agent/* require agent authentication:
EndpointDescription
GET /agent/boardsList boards accessible to agent
GET /agent/boards/{id}/tasksList tasks on board
PATCH /agent/boards/{id}/tasks/{tid}Update task status/fields
POST /agent/boards/{id}/tasks/{tid}/commentsAdd task comments
GET/POST /agent/boards/{id}/memoryRead/write board memory
POST /agent/approvals/{id}/reviewReview approvals
Source: backend/app/api/deps.py

Bootstrap Flow

The bootstrap endpoint resolves the caller’s identity:
POST /api/v1/auth/bootstrap
Authorization: Bearer <token>
Response:
{
  "id": "<user-id>",
  "email": "user@example.com",
  "name": "User Name",
  "timezone": "America/New_York",
  "active_organization_id": "<org-id>"
}
The frontend calls this on load to:
  1. Verify authentication
  2. Load user profile
  3. Determine isAdmin status for UI rendering

Organization Roles

Each user has a role in their organization:
role: "owner" | "admin" | "member"
  • Owner: Full control, can delete organization
  • Admin: Can manage agents, gateways, and boards
  • Member: Can view boards they have access to
Check membership:
GET /api/v1/organizations/me/member
Authorization: Bearer <token>
Response:
{
  "role": "owner",
  "all_boards_read": true,
  "all_boards_write": true,
  "user": {
    "id": "<user-id>",
    "email": "admin@home.local"
  }
}
Source: backend/app/api/organizations.py:402-416

Troubleshooting

”Only organization owners and admins can access agents”

Causes:
  1. Token not loaded → Check NEXT_PUBLIC_LOCAL_AUTH_TOKEN
  2. CORS blocking membership check → Add origin to CORS_ORIGINS
  3. Timezone not set → Complete onboarding at /onboarding
Verify token works:
curl http://localhost:8000/api/v1/organizations/me/member \
  -H "Authorization: Bearer $TOKEN"

CORS Issues

Ensure the browser’s origin is in CORS_ORIGINS: Backend .env:
CORS_ORIGINS=http://72.62.201.147:3000,http://72.62.201.147
Test CORS:
curl -I -X OPTIONS http://localhost:8000/api/v1/organizations/me/member \
  -H "Origin: http://72.62.201.147" \
  -H "Access-Control-Request-Method: GET"

Security Best Practices

Never commit .env files. Use .env.example as templates.
  1. Generate strong tokens for local mode (50+ characters)
  2. Use HTTPS in production for Clerk mode
  3. Rotate tokens if compromised
  4. Set minimum gateway version via GATEWAY_MIN_VERSION
  5. Enable device pairing unless using control_ui mode

See Also